//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//     Filename: SlotsXMLTranslator.cpp                                     //
//  Create date: June 18, 2004                                              //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  Description: This file implements the SlotsXMLTranslator class.         //
//                This class is a protocol translator that translates from  //
//                the RTG Slots server protocol to XML and back.            //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////
//       Copyright (c) 2004 - Realtime Gaming.  All rights reserved.        //
//////////////////////////////////////////////////////////////////////////////

#include <windows.h>
#include <stdio.h>
#include "lib.h"
#include "SlotsXMLTranslator.h"
#include "XMLDoc.h"
#include "stdncerr.h"
#include "endian.h"
#include "log.h"

//////////////////////////////////////////////////////////////////////////////
// Defines
//////////////////////////////////////////////////////////////////////////////
#define MAX_CUSTOM_DATA_LEN 255
#define MAX_ICON_LEN        255

//////////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////////
SlotsXMLTranslator::SlotsXMLTranslator( Server* pServer, Connection* pParent ) : 
													XMLTranslator( pServer, pParent, 6, 2 )
{
}

//////////////////////////////////////////////////////////////////////////////
// Destructor
//////////////////////////////////////////////////////////////////////////////
SlotsXMLTranslator::~SlotsXMLTranslator()
{
}

//////////////////////////////////////////////////////////////////////////////
// TranslateIncomingMessage
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateIncomingMessage( uint8* pGameInBuff,  
												    uint32 nGameInBuffSize, 
												    uint8* pOutBuff, 
												    uint32 nOutBuffSize )
{
    bool8 bRetVal = false;

	// Handle the messages.  Note, I'm only going to look at the first character
	//  in a small attempt to optimize a bit
	switch( m_sMsgID[0] )
	{
		case 'L':
			bRetVal = TranslateInLogin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'S':
			bRetVal = TranslateInSpin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'N':
			bRetVal = TranslateInNextSpin( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'B':
			if ( m_sMsgID[1] == 'o' )
			{
				bRetVal = TranslateInBonus( m_pDoc, pGameInBuff, nGameInBuffSize );
			}
			else if ( m_sMsgID[1] == 'a' )
			{
				bRetVal = TranslateInBalance( m_pDoc, pGameInBuff, nGameInBuffSize, GetNumBytesMsgLen() );
			}
			else
			{
				ASSERT(FALSE);
			}
			break;

		case 'C':
			bRetVal = TranslateInCustom( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'J':
			bRetVal = TranslateInJackpot( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		case 'I':
			bRetVal = TranslateInIcons( m_pDoc, pGameInBuff, nGameInBuffSize );
			break;

		default:
			String sMsg = TEXT("Error, received unknown client message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
			break;
	}

	// Report Error
	if( !bRetVal )
	{
		if ( m_sMsgID[0] == 'L' )
		{
			// Set an error into the out buffer
			ReturnLoginError( pOutBuff, nOutBuffSize );
		}
		else
		{
			// Set an error into the out buffer
			ReturnErrorMessage( TEXT("ServerError"), pOutBuff, nOutBuffSize );

			String sMsg = TEXT("Error translating incoming message: ");
			sMsg += m_sMsgID;
			Log::msgError( sMsg );
		}
	}

	return bRetVal;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutgoingMessage
//////////////////////////////////////////////////////////////////////////////
uint32 SlotsXMLTranslator::TranslateOutgoingMessage( uint8* pOutBuff, 
												     uint32 nOutBuffSize, 
												     uint8* pGameOutBuff, 
												     uint32 nGameOutBuffSize, 
												     uint32 nTotalGameBytes    )
{
	bool8 bResult = true;
	uint32 nGameBytesUsed = 0;
	uint32 nOutBytesUsed = 0;

	while ( bResult && (nGameBytesUsed < nTotalGameBytes) )
	{
		// Keep track of buffers as we might be processing more than one game
		//   message at a time.
		uint8* pCurGameBuf     = pGameOutBuff     + nGameBytesUsed;
		uint32 nCurGameBufSize = nGameOutBuffSize - nGameBytesUsed;
		uint8* pCurOutBuf      = pOutBuff         + nOutBytesUsed;
		uint32 nCurOutBufSize  = nOutBuffSize     - nOutBytesUsed;

		// Make sure buffer has not been overrun!
		if ( nOutBytesUsed > nOutBuffSize )
		{
			ASSERT(false);
			Log::msgError( "Error, out buffer overflow!" );
			return 0;
		}

		// Now the actual message handlers!
		switch( pCurGameBuf[2] )
		{
			case 'L':
				bResult = TranslateOutLogin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;
			
			case 'm':
				bResult = TranslateOutBalance( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'R':
				bResult = TranslateOutReels( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'P':
				bResult = TranslateOutPayouts( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'Y':
				bResult = TranslateOutPaylines( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'D':
				bResult = TranslateOutDenominations( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'J':
				bResult = TranslateOutJackpot( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'I':
				bResult = TranslateOutIcons( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'G':
				bResult = TranslateOutSubGame( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'S':
				bResult = TranslateOutSpin( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'V':
				bResult = TranslateOutSave( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			case 'E':
				bResult = TranslateGenericOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize, 2 );
				break;

			case 'N':
			case 'B':
			case '$':
				bResult = TranslateOutError( pCurOutBuf, nCurOutBufSize, pCurGameBuf, nCurGameBufSize );
				break;

			default:
				bResult = ReturnErrorMessage( TEXT("UnknownMessage"), pCurOutBuf, nCurOutBufSize );
				Log::msgError( "Received unknown game message: %c", pCurGameBuf[2] );
				break;
		}

		// Check and see if any errors occurred
		if ( !bResult )
		{
			ReturnErrorMessage( TEXT("ParseError"), pCurOutBuf, nCurOutBufSize );
			Log::msgError( "Error translating outgoing message: %c", pCurGameBuf[1] );
		}

		// We know that the size of the message is always stored in the first
		//  2 bytes of the client message.
		uint16 nMsgLen;
		Endian::ReceiveInt2( &nMsgLen, pCurGameBuf );
		nGameBytesUsed += nMsgLen;

		// Out XML Messages are supposed to be NULL Terminated
		nOutBytesUsed += strlen( (char*) pCurOutBuf );
		nOutBytesUsed++;  // Add one for the NULL

		DEBUGMSG( ("\n***Outgoing: %s", pCurOutBuf) );
	}

	return nOutBytesUsed;
}

//****************************************************************************
// Incoming
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateInLogin
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInLogin( XMLDoc* pDoc, 
										    uint8*  pGameInBuff, 
											uint32  nGameInBuffSize )
{
	////////////////////////////////////////////////////////////////
	// Login the user
	////////////////////////////////////////////////////////////////
    if ( !LoginUser( pDoc ) )
    {
        return false;
    }

    // Message length for slots is 2 BYTES!!!
	uint32 nOffset = 2;

	////////////////////////////////////////////////////////////////
	// Write Login for Slots
	////////////////////////////////////////////////////////////////
	pGameInBuff[nOffset++] = 'M';
	pGameInBuff[nOffset++] = 'L';
	pGameInBuff[nOffset++] = 'S';
	pGameInBuff[nOffset++] = 'M';

	////////////////////////////////////////////////////////////////
	// Add Version Info
	////////////////////////////////////////////////////////////////
	if ( !AddLoginVersionInfo( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Machine ID
	////////////////////////////////////////////////////////////////
	if ( !AddLoginMachID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add the Credit Value
	////////////////////////////////////////////////////////////////
	if ( !AddLoginCreditValue( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Add ID
	////////////////////////////////////////////////////////////////
	if ( !AddLoginID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}
   	
	////////////////////////////////////////////////////////////////
	// play for money/play for fun
	////////////////////////////////////////////////////////////////
	if ( !AddLoginMoneyType( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Cient type: CSN-7534
	////////////////////////////////////////////////////////////////
	if ( !AddClientID( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Authentication token. CSN-7075: New Authentication
	////////////////////////////////////////////////////////////////
	if ( !AddAuthenticationToken( nOffset, pDoc, pGameInBuff ) )
	{
		return false;
	}

	////////////////////////////////////////////////////////////////
	// Finally, add message size
	////////////////////////////////////////////////////////////////
	// Message length for slots is 2 BYTES!!!
	Endian::SendInt2( (uint16) nOffset, pGameInBuff );

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInSpin
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInSpin( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// Get the number of lines
	uint8 nNumLines;
	if ( !pDoc->GetSingleNodeByte( TEXT("//num_lines"), nNumLines ) )
	{
		return false;
	}

	// Now we can calculate message length
	uint16 nMsgLen = 7 + nNumLines;

	// Write Message size
	Endian::SendInt2( nMsgLen, pGameInBuff );

	// Write Message ID
	pGameInBuff[2] = 'S';

	// Write num lines
	pGameInBuff[3] = nNumLines;

    // Credit Value
    uint16 nCreditValue;
	if ( !pDoc->GetSingleNodeShort( TEXT("//credit_value"), nCreditValue ) )
	{
		return false;
	}
	Endian::SendInt2( nCreditValue, pGameInBuff+4 );

	// Bet per line
	uint8 nByte;
	if ( !pDoc->GetSingleNodeByte( TEXT("//bet_per_line"), nByte ) )
	{
		return false;
	}
	pGameInBuff[6] = nByte;

	// Do lines
	for( uint8 i = 0; i < nNumLines; i++ )
	{
		String sNode;
		sNode.sprintf( TEXT("//line_%d"), i );

		bool8 nLineNum;
		if ( !pDoc->GetSingleNodeByte( sNode, nLineNum ) )
		{
			return false;
		}
		pGameInBuff[7+i] = nLineNum;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInBonus
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInBonus( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// Get the number of items
	uint16 nNumItems;
	if ( !pDoc->GetSingleNodeShort( TEXT("//bonus_num_items"), nNumItems ) )
	{
		return false;
	}

	// Now we can calculate message length
	uint16 nMsgLen = 6 + (nNumItems * 4);   // Each item is 4 bytes long

	// Write Message size
	Endian::SendInt2( nMsgLen, pGameInBuff );

	// Write Message ID
	pGameInBuff[2] = 'B';

	// Bonus Game Type
	uint8 nType;
	if ( !pDoc->GetSingleNodeByte( TEXT("//bonus_game_type"), nType ) )
	{
		return false;
	}
	pGameInBuff[3] = nType;

	// Write number of items
    Endian::SendInt2( nNumItems, pGameInBuff+4 );

	// Write out individual items
	for( uint8 i = 0; i < nNumItems; i++ )
	{
		String sNode;
		sNode.sprintf( TEXT("//bonus_game_data_%d"), i );

		uint32 nData;
		if ( !pDoc->GetSingleNodeLong( sNode, nData ) )
		{
			return false;
		}

		Endian::SendInt4( nData, pGameInBuff + 6 + (i*4) );
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInCustom
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInCustom( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// **************************************************
	// TODO: FIGURE THIS OUT FOR MULTILINGUAL!!!!
	// **************************************************

	/*
	// Get custom data
	char szText[MAX_CUSTOM_DATA_LEN];
	if ( !pDoc->GetSingleNodeText( "//data", szText, MAX_CUSTOM_DATA_LEN ) )
	{
		return false;
	}

	// Get lenght of custom data
	uint32 nDataLen = strlen( szText );

	// Write Message size
	Endian::SendInt2( (uint16) (nDataLen + 7), pGameInBuff );

	// Write Data Length
	Endian::SendInt4( nDataLen, pGameInBuff + 3 );

	// Write Data
	for( uint32 i = 0; i < nDataLen; i++ )
	{
		*(pGameInBuff + 7 + i) = szText[i];
	}

	*/
	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInJackpot
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInJackpot( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// Write Message size
	Endian::SendInt2( (uint16) 3, pGameInBuff );

	// Add message id
	pGameInBuff[2] = 'J';

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInIcons
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInIcons( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// Write Message size
	Endian::SendInt2( (uint16) 3, pGameInBuff );

	// Add message id
	pGameInBuff[2] = 'I';

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateInNextSpin
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateInNextSpin( XMLDoc* pDoc, uint8* pGameInBuff,  uint32 nGameInBuffSize )
{
	// Write Message size
	Endian::SendInt2( (uint16) 3, pGameInBuff );

	// Add message id
	pGameInBuff[2] = 's';

	return true;
}

//****************************************************************************
// Outgoing
//****************************************************************************

//////////////////////////////////////////////////////////////////////////////
// TranslateOutLogin
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutLogin( uint8* pOutBuff, 
										     uint32 nOutBuffSize, 
										     uint8* pGameOutBuff, 
										     uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Login") ) )
	{
		return false;
	}

// Check message length
#ifdef DEBUG
	uint16 nMsgLen;
	Endian::ReceiveInt2( &nMsgLen, pGameOutBuff );
	ASSERT( nMsgLen == 10 );
#endif

	// Balance 
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+3 );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}

	// Minimum Bet
	uint8 nMaxBet = pGameOutBuff[7];
	if ( !doc.AddRootChildNode( TEXT("max_bet"), (uint32) nMaxBet ) )
	{
		return false;
	}

	// Progressive
	bool8 bFlag = pGameOutBuff[8];
	if ( !doc.AddRootChildNode( TEXT("progressive"), bFlag ) )
	{
		return false;
	}

	// Fast Play
	bFlag = pGameOutBuff[9];
	if ( !doc.AddRootChildNode( TEXT("fast_play"), bFlag ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutReels
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutReels( uint8* pOutBuff, 
										     uint32 nOutBuffSize, 
										     uint8* pGameOutBuff, 
										     uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Reels") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num Reels
	uint8 nNumReels = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_reels"), (uint32) nNumReels ) )
	{
		return false;
	}

	// Loop through all reels
	for( uint8 i = 0; i < nNumReels; i++ )
	{
		// Add the child node for the reel
		String sReelNode;
		sReelNode.sprintf( TEXT("reel_%d"), i );
		
		if ( !doc.AddRootChildNode( sReelNode ) )
		{
			return false;
		}

		// Get the number of icons for this reel
		uint8 nNumIcons = pGameOutBuff[nOffset++];

		String sQuery = TEXT("//");
		sQuery += sReelNode;

		// Write out num icons
		if ( !doc.AddChildNode( sQuery, TEXT("num_icons"), (uint32) nNumIcons ) )
		{
			return false;
		}

		// Loop through all the icons
		for( uint8 j = 0; j < nNumIcons; j++ )
		{
			// Get the icon value
			uint8 nIcon = pGameOutBuff[nOffset++];

			// Add the child node for the icon
			String sIconNode;
			sIconNode.sprintf( TEXT("icon_%d"), j );

			if ( !doc.AddChildNode( sQuery, sIconNode, (uint32) nIcon ) )
			{
				return false;
			}
		}
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutPayouts
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutPayouts( uint8* pOutBuff, 
										       uint32 nOutBuffSize, 
										       uint8* pGameOutBuff, 
										       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Payouts") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num Columns
	uint8 nNumCols = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_columns"), (uint32) nNumCols ) )
	{
		return false;
	}

	// Loop through all columns
	for( uint8 i = 0; i < nNumCols; i++ )
	{
		// Add the child node for the column
		String sColNode;
		sColNode.sprintf( TEXT("column_%d"), i );

		if ( !doc.AddRootChildNode( sColNode ) )
		{
			return false;
		}

		// Get the number of payouts for this column
		uint8 nNumPayouts = pGameOutBuff[nOffset++];

		String sQuery = TEXT("//");
		sQuery += sColNode;
		
		// Write out num payouts
		if ( !doc.AddChildNode( sQuery, TEXT("num_payouts"), (uint32) nNumPayouts ) )
		{
			return false;
		}

		// Loop through all the payouts
		for( uint8 j = 0; j < nNumPayouts; j++ )
		{
			// Get the payout value
			int32 nPayout;
			Endian::ReceiveInt4( &nPayout, pGameOutBuff+nOffset );
			nOffset += 4;

			// Add the child node for the payout
			String sPayNode;
			sPayNode.sprintf( TEXT("payout_%d"), j );

			if ( !doc.AddChildNode( sQuery, sPayNode, (uint32) nPayout ) )
			{
				return false;
			}
		}
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutPaylines
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutPaylines( uint8* pOutBuff, 
										        uint32 nOutBuffSize, 
										        uint8* pGameOutBuff, 
										        uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Paylines") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num paylines
	uint8 nNumPL = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_paylines"), (uint32) nNumPL ) )
	{
		return false;
	}
	
	// Num reels
	uint8 nNumReels = pGameOutBuff[nOffset++];
	// We only have a gutsid with more than 5 reels: Football Frenzy. And even with this one we want to send to the client only 5 reels,
	// so fix this value to a maximum of 5
	nNumReels = nNumReels > 5 ? 5 : nNumReels;
	if ( !doc.AddRootChildNode( TEXT("num_reels"), (uint32) nNumReels ) )
	{
		return false;
	}

	// Loop through all the paylines
	for( uint8 i = 0; i < nNumPL; i++ )
	{
		// Add the child node for the payline
		String sPLNode;
		sPLNode.sprintf( TEXT("payline_%d"), i );

		if ( !doc.AddRootChildNode( sPLNode ) )
		{
			return false;
		}

		String sQuery = TEXT("//");
		sQuery += sPLNode;

		// Loop through all the reels
		for( uint8 j = 0; j < nNumReels; j++ )
		{
			// Get the payline pos offset
			uint8 nPLOffset = pGameOutBuff[nOffset++];

			// Add the child node for each pos offset
			String sOffset;
			sOffset.sprintf( TEXT("payline_pos_offset_%d"), j );

			if ( !doc.AddChildNode( sQuery, sOffset, (uint32) nPLOffset ) )
			{
				return false;
			}
		}
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutDenominations
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutDenominations( uint8* pOutBuff, 
										             uint32 nOutBuffSize, 
										             uint8* pGameOutBuff, 
										             uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Denominations") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num Denominations
	uint8 nNumDenom = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_denominations"), (uint32) nNumDenom ) )
	{
		return false;
	}
	
	// Loop through all the Denominations
	for( uint8 i = 0; i < nNumDenom; i++ )
	{
		// Add the child node for the Denomination
		String sDNode;
		sDNode.sprintf( TEXT("denomination_%d"), i );

		if ( !doc.AddRootChildNode( sDNode ) )
		{
			return false;
		}

		// Create string for adding child nodes
		String sQuery = TEXT("//");
		sQuery += sDNode;

        // Machine Sub ID
        uint16 nShortVal;
		Endian::ReceiveInt2( &nShortVal, pGameOutBuff+nOffset );
		nOffset += 2;

        if ( !doc.AddChildNode( sQuery, TEXT("machine_sub_id"), (uint32) nShortVal ) )
		{
			return false;
		}
        
        // Credit Value
		Endian::ReceiveInt2( &nShortVal, pGameOutBuff+nOffset );
		nOffset += 2;

        if ( !doc.AddChildNode( sQuery, TEXT("credit_value"), (uint32) nShortVal ) )
		{
			return false;
		}
        
        // Is default
		uint8 nVal = pGameOutBuff[nOffset++];
        if ( !doc.AddChildNode( sQuery, TEXT("default"), (bool8) nVal ) )
		{
			return false;
		}
    }

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutJackpot
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutJackpot( uint8* pOutBuff, 
										       uint32 nOutBuffSize, 
										       uint8* pGameOutBuff, 
										       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Jackpot") ) )
	{
		return false;
	}

// Check message length
#ifdef DEBUG
	uint16 nMsgLen;
	Endian::ReceiveInt2( &nMsgLen, pGameOutBuff );
	ASSERT( nMsgLen == 11 );
#endif

	// Jackpot 
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+3 );
	if ( !doc.AddRootChildNode( TEXT("jackpot"), (uint32) nValue ) )
	{
		return false;
	}

	// Rate 
	Endian::ReceiveInt4( &nValue, pGameOutBuff+7 );
	if ( !doc.AddRootChildNode( TEXT("increase"), (uint32) nValue ) )
	{
		return false;
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutIcons
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutIcons( uint8* pOutBuff, 
										     uint32 nOutBuffSize, 
									         uint8* pGameOutBuff, 
								    	     uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Icons") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// GutsID
	int32 nGutsID;
	Endian::ReceiveInt4( &nGutsID, pGameOutBuff + nOffset );
	nOffset += 4;
	if ( !doc.AddRootChildNode( TEXT("gutsid"), (uint32) nGutsID ) )
	{
		return false;
	}
	
	// Num Icons
	uint8 nNumIcons = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_icons"), (uint32) nNumIcons ) )
	{
		return false;
	}

	// Loop through all the Icons
	for( uint8 i = 0; i < nNumIcons; i++ )
	{
		// Get the lenght of the icon name
		uint8 nLen = pGameOutBuff[nOffset++];

		// Make sure we have enough buffer space
		ASSERT( nLen < MAX_ICON_LEN );

		// Now, get the actual string
		char szIconName[MAX_ICON_LEN];
		for( uint8 ch = 0; ch < nLen; ch++ )
		{
			szIconName[ch] = pGameOutBuff[nOffset++];
		}
		// Add NULL
		szIconName[ch] = 0;

		// Add the child node for the icon name
		String sIconNode;
		sIconNode.sprintf( TEXT("icon_name_%d"), i );		

		if ( !doc.AddRootChildNode( sIconNode ) )
		{
			return false;
		}

	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSubGame
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutSubGame( uint8* pOutBuff, 
										       uint32 nOutBuffSize, 
									           uint8* pGameOutBuff, 
								    	       uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("SubGame") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Num of items
	uint8 nNumItems = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_items"), (uint32) nNumItems ) )
	{
		return false;
	}

	// Loop through all the items
	for( uint8 i = 0; i < nNumItems; i++ )
	{
		// Get the Data for each item
        int32 nValue;
    	Endian::ReceiveInt4( &nValue, pGameOutBuff + nOffset );
        nOffset += 4;

		// Add the child node for the payline
		String sItemNode;
		sItemNode.sprintf( TEXT("element_%d"), i );

		if ( !doc.AddRootChildNode( sItemNode, (uint32) nValue ) )
		{
			return false;
		}
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSpin
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutSpin( uint8* pOutBuff, 
										    uint32 nOutBuffSize, 
									        uint8* pGameOutBuff, 
								    	    uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("Spin") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

	// Balance 
	int32 nValue;
	Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
	if ( !doc.AddRootChildNode( TEXT("balance"), (uint32) nValue ) )
	{
		return false;
	}
	nOffset += 4;

	// Num Spins
	uint8 nNumSpins = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("num_spins"), (uint32) nNumSpins ) )
	{
		return false;
	}

	// Num Spins in this message
	uint8 nMsgSpins = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("msg_spins"), (uint32) nMsgSpins ) )
	{
		return false;
	}

	// Starting spin index
	uint8 nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("start_spin"), (uint32) nVal ) )
	{
		return false;
	}
	
	// Message Type
	nVal = pGameOutBuff[nOffset++];
	if ( !doc.AddRootChildNode( TEXT("msg_type"), (uint32) nVal ) )
	{
		return false;
	}

	// Loop through all spins
	for( uint8 i = 0; i < nMsgSpins; i++ )
	{
		// Add the child node for each spin
		String sSpinNode;
		sSpinNode.sprintf( TEXT("spin_%d"), i );

		if ( !doc.AddRootChildNode( sSpinNode ) )
		{
			return false;
		}

		// Get the number of Reels
		uint8 nNumReels = pGameOutBuff[nOffset++];

		String sSpinQuery = TEXT("//");
		sSpinQuery += sSpinNode;

		// Add number of reels
		if ( !doc.AddChildNode( sSpinQuery, TEXT("num_reels"), (uint32) nNumReels ) )
		{
			return false;
		}

		// Loop through all the Reels
		for( uint8 j = 0; j < nNumReels; j++ )
		{
			// Get the wheel stop for each
			uint8 nWheelStop = pGameOutBuff[nOffset++];

			// Add the child node for the wheel stop
			String sWSNode;
			sWSNode.sprintf( TEXT("wheel_stop_%d"), j );

			if ( !doc.AddChildNode( sSpinQuery, sWSNode, (uint32) nWheelStop ) )
			{
				return false;
			}
		}
		
		// Get the number of Lines
		uint8 nNumLines = pGameOutBuff[nOffset++];

		// Add number of lines
		if ( !doc.AddChildNode( sSpinQuery, TEXT("num_lines"), (uint32) nNumLines ) )
		{
			return false;
		}	
		
		// Loop through all the Lines
		for( j = 0; j < nNumLines; j++ )
		{
			// Get the line number
			uint8 nLinNum = pGameOutBuff[nOffset++];

			// Add the child node for the line 
			String sLine;
			sLine.sprintf( TEXT("line_%d"), j );

			if ( !doc.AddChildNode( sSpinQuery, sLine ) )
			{
				return false;
			}

			String sLineQuery = sSpinQuery;
			sLineQuery += TEXT("//");
			sLineQuery += sLine;

			// Line Number
			if ( !doc.AddChildNode( sLineQuery, TEXT("line_num"), (uint32) nLinNum ) )
			{
				return false;
			}

			// Win Type
			uint8 nWinType = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
			{
				return false;
			}

			// Win Amount
			int32 nValue;
			Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_amt"), (uint32) nValue ) )
			{
				return false;
			}
			nOffset += 4;

			// Jackpot?
			bool8 bJackpot = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
			{
				return false;
			}
		}	

		// Get the number of Scatters
		uint8 nNumScatters = pGameOutBuff[nOffset++];

		// Add number of lines
		if ( !doc.AddChildNode( sSpinQuery, TEXT("num_scatters"), (uint32) nNumScatters ) )
		{
			return false;
		}	
		
		// Loop through all the Lines
		for( j = 0; j < nNumScatters; j++ )
		{
			// Add the child node for the line 
			String sLine;
			sLine.sprintf( TEXT("scatter_%d"), j );
			
			if ( !doc.AddChildNode( sSpinQuery, sLine ) )
			{
				return false;
			}

			String sLineQuery = sSpinQuery;
			sLineQuery += TEXT("//");
			sLineQuery += sLine;

			// Scatter Position
			int32 nScatterPos;
			Endian::ReceiveInt4( &nScatterPos, pGameOutBuff+nOffset );
			if ( !doc.AddChildNode( sLineQuery, TEXT("scatter_pos"), (uint32) nScatterPos ) )
			{
				return false;
			}
            nOffset += 4;

			// Win Type
			uint8 nWinType = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
			{
				return false;
			}

			// Win Amount
			int32 nValue;
			Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_amt"), (uint32) nValue ) )
			{
				return false;
			}
			nOffset += 4;

			// Jackpot?
			bool8 bJackpot = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
			{
				return false;
			}
		}	
	}

	// Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutSave
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutSave( uint8* pOutBuff, 
										    uint32 nOutBuffSize, 
									        uint8* pGameOutBuff, 
								    	    uint32 nGameOutBuffSize )
{
	XMLDoc doc;

	if ( !doc.StartNew() )
	{
		return false;
	}

	if ( !doc.CreateRootNode( TEXT("game_msg") ) )
	{
		return false;
	}

	if ( !doc.AddRootChildNode( TEXT("msg_id"), TEXT("SavedGame") ) )
	{
		return false;
	}

	// Starting offset for mesage translation
	uint32 nOffset = 3;

    // Credit Value
    // just skip for now
    nOffset += 2;

	// Num Spins
	uint8 nNumSpins = pGameOutBuff[nOffset++];

	// Write out num spins
	if ( !doc.AddRootChildNode( TEXT("num_spins"), (uint32) nNumSpins ) )
	{
		return false;
	}

	// Loop through all spins
	for( uint8 i = 0; i < nNumSpins; i++ )
	{
		// Add the child node for each spin
		String sSpinNode;
		sSpinNode.sprintf( TEXT("spin_%d"), i );

		if ( !doc.AddRootChildNode( sSpinNode ) )
		{
			return false;
		}

		// Get the number of Reels
		uint8 nNumReels = pGameOutBuff[nOffset++];

		String sSpinQuery = TEXT("//");
		sSpinQuery += sSpinNode;

		// Add number of reels
		if ( !doc.AddChildNode( sSpinQuery, TEXT("num_reels"), (uint32) nNumReels ) )
		{
			return false;
		}

		// Loop through all the Reels
		for( uint8 j = 0; j < nNumReels; j++ )
		{
			// Get the wheel stop for each
			uint8 nWheelStop = pGameOutBuff[nOffset++];

			// Add the child node for the wheel stop
			String sWSNode;
			sWSNode.sprintf( TEXT("wheel_stop_%d"), j );

			if ( !doc.AddChildNode( sSpinQuery, sWSNode, (uint32) nWheelStop ) )
			{
				return false;
			}
		}
		
		// Get the number of Lines
		uint8 nNumLines = pGameOutBuff[nOffset++];

		// Add number of lines
		if ( !doc.AddChildNode( sSpinQuery, TEXT("num_lines"), (uint32) nNumLines ) )
		{
			return false;
		}	
		
		// Loop through all the Lines
		for( j = 0; j < nNumLines; j++ )
		{
			// Get the line number
			uint8 nLinNum = pGameOutBuff[nOffset++];

			// Add the child node for the line 
			String sLine;
			sLine.sprintf( TEXT("line_%d"), j );

			if ( !doc.AddChildNode( sSpinQuery, sLine ) )
			{
				return false;
			}

			String sLineQuery = sSpinQuery;
			sLineQuery += TEXT("//");
			sLineQuery += sLine;

			// Line Number
			if ( !doc.AddChildNode( sLineQuery, TEXT("line_num"), (uint32) nLinNum ) )
			{
				return false;
			}

			// Jackpot?
			bool8 bJackpot = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
			{
				return false;
			}

			// Win Type
			uint8 nWinType = pGameOutBuff[nOffset++];
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
			{
				return false;
			}

			// Win Amount
			int32 nValue;
			Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
			if ( !doc.AddChildNode( sLineQuery, TEXT("win_amt"), (uint32) nValue ) )
			{
				return false;
			}
			nOffset += 4;
		}	
	
	    // Get the number of Scatters
	    uint8 nNumScatters = pGameOutBuff[nOffset++];

	    // Add number of lines
	    if ( !doc.AddChildNode( sSpinQuery, TEXT("num_scatters"), (uint32) nNumScatters ) )
	    {
		    return false;
	    }	
	    
	    // Loop through all the Lines
	    for( j = 0; j < nNumScatters; j++ )
	    {
		    // Add the child node for the line 
			String sLine;
			sLine.sprintf( TEXT("scatter_%d"), j );

		    if ( !doc.AddChildNode( sSpinQuery, sLine ) )
		    {
			    return false;
		    }

			String sLineQuery = sSpinQuery;
			sLineQuery += TEXT("//");
			sLineQuery += sLine;

		    // Scatter Position
		    int32 nScatterPos;
		    Endian::ReceiveInt4( &nScatterPos, pGameOutBuff+nOffset );
		    if ( !doc.AddChildNode( sLineQuery, TEXT("scatter_pos"), (uint32) nScatterPos ) )
		    {
			    return false;
		    }
            nOffset += 4;

		    // Win Type
		    uint8 nWinType = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sLineQuery, TEXT("win_type"), (uint32) nWinType ) )
		    {
			    return false;
		    }

		    // Win Amount
		    int32 nValue;
		    Endian::ReceiveInt4( &nValue, pGameOutBuff+nOffset );
		    if ( !doc.AddChildNode( sLineQuery, TEXT("win_amt"), (uint32) nValue ) )
		    {
			    return false;
		    }
		    nOffset += 4;

		    // Jackpot?
		    bool8 bJackpot = pGameOutBuff[nOffset++];
		    if ( !doc.AddChildNode( sLineQuery, TEXT("jackpot"), bJackpot ) )
		    {
			    return false;
		    }
	    }	
    }

    // Done - send the actual XML
	if ( !doc.SendXML( pOutBuff, nOutBuffSize ) )
	{
		return false;
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////////
// TranslateOutError
//////////////////////////////////////////////////////////////////////////////
bool8 SlotsXMLTranslator::TranslateOutError( uint8* pOutBuff, 
										     uint32 nOutBuffSize, 
										     uint8* pGameOutBuff, 
										     uint32 nGameOutBuffSize )
{
	bool8 bRetVal = false;

	if ( pGameOutBuff[2] == 'N' )
	{
        // kharmon.  02-21-07.  MCA #29819.  Need to send back different messages for auto and admin logoffs.
        String strErrorName = "InvalidAction";
        int16 nLen = 0;
        Endian::ReceiveInt2( &nLen, pGameOutBuff );

        if(nLen==4) // the extra byte has the error code info.
        {
            switch(pGameOutBuff[3])
            {
                case TrackSQL::AdminLogout:
                {
                    strErrorName = "AdminLogout";
                    break;
                }
                case TrackSQL::AutoLogout:
                {
                    strErrorName = "TimedOut";
                    break;
                }
            }            
        }

        bRetVal = ReturnErrorMessage( strErrorName, pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == '$' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InsufficientFunds"), pOutBuff, nOutBuffSize );
	}
	else if ( pGameOutBuff[2] == 'B' )
	{
		bRetVal = ReturnErrorMessage( TEXT("InvalidBet"), pOutBuff, nOutBuffSize );
	}

	return bRetVal;
}